﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using JetBlack.Diagnostics;
using JetBlack.IO;
using JetBlack.SimpleTopicBus.Messages;

namespace JetBlack.SimpleTopicBus.Distributor
{
    /// <summary>
    /// The Interactor handles the network reading and writing.
    /// </summary>
    internal class Interactor : IDisposable, IEquatable<Interactor>, IComparable<Interactor>
    {
        public event Action<Interactor> OnClosed;
        public event Action<Interactor, Message> OnMessage;

        public readonly int Id;
        private readonly TcpClient tcpClient;
        private readonly Thread thread;
        private bool isConnected;
        private NetworkReader reader;
        private NetworkWriter writer;
        private NetworkStream networkStream;

        public Interactor(TcpClient tcpClient, int id)
        {
            this.tcpClient = tcpClient;
            this.Id = id;

            thread = new Thread(Run);
            thread.Name = String.Format("interactor/{0}", RemoteEndPoint);

            networkStream = tcpClient.GetStream();
            reader = new NetworkReader(networkStream);
            writer = new NetworkWriter(networkStream);

            isConnected = true;
        }

        public void Start()
        {
            thread.Start();
        }

        private void Run()
        {
            AppEnvironment.TraceSource.Verbose("Starting interactor: {0}", this);

            while (IsConnected)
                IsConnected = Dispatch();

            if (OnClosed != null)
                OnClosed(this);
        }

        internal bool Dispatch()
        {
            try
            {
                Message message = Message.Read(reader);
                if (OnMessage != null)
                    OnMessage(this, message);
            }
            catch (System.IO.EndOfStreamException)
            {
                return false;
            }
            catch (IOException ex)
            {
                if (ex.InnerException != null && ex.InnerException is SocketException)
                {
                    SocketException socketException = (SocketException)ex.InnerException;
                    if (socketException.ErrorCode == 10060)
                        return true; // Timeout
                }

                AppEnvironment.TraceSource.Verbose("Interactor disconnected: {0}", this);
                return false;
            }

            return true;
        }

        public void SendMessage(Message message)
        {
            lock (writer)
                message.Write(writer);
        }

        public bool IsConnected
        {
            get { lock (this) return isConnected; }
            set { lock (this) isConnected = value; }
        }

        public IPEndPoint LocalEndPoint
        {
            get { return (System.Net.IPEndPoint)tcpClient.Client.LocalEndPoint; }
        }

        public IPEndPoint RemoteEndPoint
        {
            get { return (System.Net.IPEndPoint)tcpClient.Client.RemoteEndPoint; }
        }

        public Socket Socket
        {
            get { return tcpClient.Client; }
        }

        public override string ToString()
        {
            return string.Format("{0}/{1}", Id, RemoteEndPoint);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Interactor);
        }

        public bool Equals(Interactor other)
        {
            return other != null && other.Id == Id;
        }

        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public int CompareTo(Interactor other)
        {
            return (other == null ? 1 : Id - other.Id);
        }

        public void Dispose()
        {
            tcpClient.Close();

            if (thread != null && thread.IsAlive)
            {
                IsConnected = false;
                thread.Join();
            }
        }
    }
}
